package MaquinaP;
import java.io.BufferedReader;
import java.io.FileInputStream;
import java.io.FileReader;
import java.io.IOException;
import java.io.InputStreamReader;
import java.io.ObjectInputStream;
import java.util.ArrayList;

import MaquinaP.Estructura.Pila;
import MaquinaP.Estructura.TablaSimbolos;
import MaquinaP.Estructura.Variable;

//interprete para la ejecucion del codigo objeto
public class Interprete {
	
	private ArrayList<String> memProg;// Lista con las instruccion que ejecutar
	private int tipo;// tipo de ejecucion(normal o traza)
	private int pc;//contador de instrucciones
	private boolean H;//booleano para el control, cuando se pone a false se acaba la ejecucion
	private Pila pila;//pila con los datos
	private TablaSimbolos variables;//tabla con la informacion de las variables
	private Estado estado;//estado de la maquina
	private String linea;//linea que se esta procesando
	
	private static enum Estado {DECVAR,DECAC}
	
	//constructora, creamos los atributos y las inicializamos
	public Interprete(String ar,int t) throws IOException, ClassNotFoundException{
		
		memProg = dameProg(ar);//pedimos las intrucciones ya leidas
		tipo = t;
		pc = 0;
		H = true;	
		variables = new TablaSimbolos();
		pila = new Pila(variables);
		System.out.println(memProg);
		estado = Estado.DECVAR;
		
	}
	//coge el nombre del archivo y extrae la informacion, guardandola en la memoria de programa
	private ArrayList<String> dameProg(String ar) throws IOException, ClassNotFoundException{
		
		FileInputStream fos = new FileInputStream("bytes");
    	ObjectInputStream oos = new ObjectInputStream(fos);
    	String s2 = "";
		s2 = (String) oos.readObject();
    	oos.close();
    	ArrayList<String> all = new ArrayList<String>();
    	String array[] = s2.split("\n");    	
    	for(int i = 0;i<array.length;i++)
    		all.add(array[i]);
    	all.add("stop");
		BufferedReader reader= new BufferedReader(new FileReader(ar));
		ArrayList<String> s = new ArrayList<String>();
	    String lin= reader.readLine();
	    while(lin!=null) {
	      s.add(lin);
	      lin = reader.readLine();
	    }
	    s.add("stop");
	    reader.close();
		return s;
	}
	
	public void ejecucion() throws IOException{
		//vemos que no a cambiado H y vemos en que estado estamos, y que debemos hacer dependiendo de la linea leida
		while(H){
			linea = memProg.get(pc);
			switch(estado) {
		    	case DECVAR: 
		    		if(esAmpersan()) transita(Estado.DECAC);
		    		else if (esFin()) transitaFin();
		    		else  decVariable();
		    		break;
		    	case DECAC:
		    		if (esFin()) transitaFin();
		    		else if (esEntrada()) entrada();
		    		else if (esSalida()) salida();
		    		else if(esApila())	apilar();
		    		else if(esApilaDir())apilarDir();
		    		else if(esDesapilaDir())	desapilarDir();
		    		else if(esSuma())sumar();
		    		else if(esMult())multiplicar();
		    		else if(esResta()) restar();
		    		else if(esDiv())   dividir();
		    		else if(esMenor()) menor();
		    		else if(esMayor())  mayor();
		    		else if(esMenorIgual())  menorIgual();
		    		else if(esMayorIgual()) mayorIgual();
		    		else if(esIgual()) igual();
		    		else if(esDistinto()) distinto();
		    		else if(esModulo()) modulo();
		    		else if(esOr()) or();
		    		else if(esAnd()) and();
		    		else if(esNegacion()) negacion();
		    		else if(esMenos()) menos();
		    		else if(esCastInt()) castInt();
		    		else if(esCastReal()) castReal();
		    		break;
			}
		}
		System.out.println("Fin de la ejecucion");
	}
	
	
	//lee la linea hasta que llega a fin de linea o se encuentra un espacio
	private String dameCadenaEsp(int i){
		String s="";
		while((i<linea.length())&&(linea.charAt(i)!=' ')){
			s+=linea.charAt(i);
			i++;
		}
		return s;
	}
	//lee ka cadeba hasta que llega a fin de linea o un parentesis abierto
	private String dameCadenaP(){
		String s="";
		int i = 0;
		while((i<linea.length())&&(linea.charAt(i)!='(')){
			s+=linea.charAt(i);
			i++;
		}
		return s;
	}
	
//-----------RECONOCE QUE OPERACION O QUE TIPO DE OPERACION DEBEMOS HACER--------------------
	private boolean esAmpersan(){
		return linea.equals("&");
	}
	private boolean esFin(){
		return linea.equals("stop");
	}
	private boolean esEntrada(){
		String s = dameCadenaEsp(0);
		return s.equals("in");
	}
	private boolean esSalida(){
		String s = dameCadenaEsp(0);
		return s.equals("out");
	}
	private boolean esApila(){
		String s = dameCadenaP();
		return s.equals("apila");
	}
	private boolean esApilaDir(){
		String s = dameCadenaP();
		return s.equals("apila-dir");
	}
	private boolean esDesapilaDir(){
		String s = dameCadenaP();
		return s.equals("desapila-dir");
	}
	private boolean esSuma(){
		String  s = dameCadenaEsp(0);
		return s.equals("sumar");
	}
	private boolean esMult(){
		String  s = dameCadenaEsp(0);
		return s.equals("multiplicar");
	}
	private boolean esResta(){
		String  s = dameCadenaEsp(0);
		return s.equals("restar");
	}
	private boolean esDiv(){
		String  s = dameCadenaEsp(0);
		return s.equals("dividir");
	}
	private boolean esMenor(){
		String s = dameCadenaEsp(0);
		return s.equals("<");
	}
	private boolean esMayor(){
		String s = dameCadenaEsp(0);
		return s.equals(">");
	}
	private boolean esMenorIgual(){
		String s = dameCadenaEsp(0);
		return s.equals("<=");
	}
	private boolean esMayorIgual(){
		String s = dameCadenaEsp(0);
		return s.equals(">=");
	}
	private boolean esIgual(){
		String s = dameCadenaEsp(0);
		return s.equals("==");
	}
	private boolean esDistinto(){
		String s = dameCadenaEsp(0);
		return s.equals("!=");
	}
	private boolean esModulo(){
		String s = dameCadenaEsp(0);
		return s.equals("%");
	}
	private boolean esOr(){
		String s = dameCadenaEsp(0);
		return s.equals("||");
	}
	private boolean esAnd(){
		String s = dameCadenaEsp(0);
		return s.equals("&&");
	}
	private boolean esNegacion(){
		String s = dameCadenaEsp(0);
		return s.equals("!");
	}
	private boolean esMenos(){
		String s = dameCadenaEsp(0);
		return s.equals("-");
	}
	private boolean esCastInt(){
		String s = dameCadenaEsp(0);
		return s.equals("int");
	}
	private boolean esCastReal(){
		String s = dameCadenaEsp(0);
		return s.equals("real");
	}
	
	//--------------------------------------------------------------------------------------
	
	//-----------FUNCIONES PARA TRANSITAR EN LOS ESTADOS-----------------------------------
	private void transita(Estado sigEstado){
		estado = sigEstado;
		pc++;
	}
	private void transitaFin(){
		H = false;
		System.out.println(variables.toString());
		System.out.println(pila.toString());
	}
	
//---------------------------------------------------------------------------------------------
	
//---------------FUNCIONES PARA EJECUTAR LAS ACCIONES CORRESPONDIENTES---------------------------	
	
	private void decVariable() throws IOException{

		traza("Declaracion de variable");//si es de tipo traza, se enseñaran los datos pertinentes mas la accion a ejecutar
			
		int index = 0;
		String s = "";
		//leemos el tipo de la variable
		while (linea.charAt(index)!=' '){
			s+=linea.charAt(index);
			index++;
		}
		String tip = s;
		//leemos el nombre de la variable
		s="";
		index++;
		while (linea.charAt(index)!=':'){
			s+=linea.charAt(index);
			index++;
		}
		String nombre = s;
		
		s="";
		index = linea.indexOf("[",index)+1;
		//leemos el numero de memoria asignado
		while (linea.charAt(index)!=']'){
			s+=linea.charAt(index);
			index++;
		}
		int num = Integer.valueOf(s);
		Variable v = new Variable(nombre,0,tip);
		variables.anadeID(v,num);//introducimos la variable
		//pasamos a la siguiente instruccion			
		pc++;
	}
	//metodo para ejecutar la instruccion in
	private void entrada() throws IOException{

		traza("Entrada de datos");
		int index = 3;
		String s="";
		//leemos la direccion donde guardar el numero
		while(index<linea.length()){
			s+=linea.charAt(index);
			index++;
		}
		int num = Integer.valueOf(s);
		//leemos el valor de la consola
		System.out.print("Escriba el valor de la variable "+variables.get(num).getNombre()+" : ");
		BufferedReader br = new BufferedReader(new InputStreamReader(System.in));
		s = br.readLine();
		Variable v = variables.get(num);
		String tipo = v.getTipo();
		//lo guardamos como el tipo de la variable
		if(tipo.equals("int"))
			v.setValor(Integer.valueOf(s));
		else if (tipo.equals("real"))
			v.setValor(Double.valueOf(s));
		
		pc++;
	}
	//metodo para ejecutar la funcion out
	private void salida() throws IOException{
		
		traza("Salida de datos");
		pila.out();
		pc++;
	}
	//metodo para ejecutar la funcion apilar
	public void apilar() throws IOException{
				
		traza("Apilar");		
		int index;
		index = linea.indexOf('(')+1;
		String s = "";
		String aux = "";
		boolean b = false;
		boolean real = false;
		//leemos el numero, pudiendo ser entero o real
		while(linea.charAt(index)!=')'){
			//por si lleva exponente
			if (linea.charAt(index)=='e'){
				b = true;
				real = true;
			}
			//si es decimal
			if(linea.charAt(index)=='.')
				b = true;
			if(!real)
				s+=linea.charAt(index);
			else
				aux +=linea.charAt(index);
			
		
			index++;
		}
		if(real){
			aux = aux.substring(1, aux.length());//quitamos la e, que habiamos incluido 
			double d;
			d = Double.valueOf(aux);//convertimos el numero a real
			d = Math.pow(10, d);//elevamos 10 a d para pasar el exponente a numero
			pila.apila(Double.valueOf(s)*d);
		}
		//apilamos el numero
		else if (b == true)
			pila.apila(Double.valueOf(s));
		else
			pila.apila(Integer.valueOf(s));
		
		pc++;
	}
	//metodo para ejecutar la funcion apilar una direccion
	public void apilarDir() throws IOException{
		//apilamos en la pila el valor que haya en esa posicion de memoria
		traza("Apilar direccion");
		int index;
		index = linea.indexOf('(')+1;
		String s = "";
		while(linea.charAt(index)!=')'){
			s+=linea.charAt(index);
			index++;
		}
		pila.apilaDir(Integer.valueOf(s));
		pc++;
	}
	//metodo para ejecutar la funcion desapilar
	public void desapilarDir() throws IOException{
		//desapilamos el valor que haya en la pila en la variable indicada
		traza("Desapilar direccion");
		int index;
		index = linea.indexOf('(')+1;
		String s = "";
		while(linea.charAt(index)!=')'){
			s+=linea.charAt(index);
			index++;
		}
		pila.desapilaDir(Integer.valueOf(s));
		pc++;
	}
	//metodo para ejecutar la funcion sumar
	public void sumar() throws IOException{
		traza("Suma");
		pila.suma();
		pc++;
	}
	//metodo para ejecutar la funcion multiplicar
	public void multiplicar()throws IOException{

		traza("Multiplicacion");
		pila.multiplicacion();
		pc++;
	}
	//metodo para ejecutar la funcion resta
	public void restar()throws IOException{
		traza("Resta");
		pila.resta();
		pc++;
	}
	//metodo para ejecutar la funcion dividir
	public void dividir()throws IOException{
		traza("Division");
		H = pila.division();
		pc++;
	}
	//metodo para ejecutar la funcion menor
	public void menor()throws IOException{
		traza("Menor");
		pila.menor();
		pc++;
	}
	//metodo para ejecutar la funcion mayor
	public void mayor()throws IOException{
		traza("Mayor");
		pila.mayor();
		pc++;
	}
	//metodo para ejecutar la funcion menor o igual
	public void menorIgual()throws IOException{
		traza("Menor o igual");
		pila.menorIgual();
		pc++;
	}
	//metodo para ejecutar la funcion mayor o igual
	public void mayorIgual()throws IOException{
		traza("Mayor o igual");
		pila.mayorIgual();
		pc++;
	}
	//metodo para ejecutar la funcion igual
	public void igual()throws IOException{
		traza("Igual");
		pila.igual();
		pc++;
	}
	//metodo para ejecutar la funcion distinto
	public void distinto()throws IOException{
		traza("Distinto");
		pila.distinto();
		pc++;
	}
	//metodo para ejecutar la funcion modulo
	public void modulo()throws IOException{
		traza("Modulo");
		H = pila.modulo();
		pc++;
	}
	//metodo para ejecutar la funcion or
	public void or()throws IOException{
		traza("Or");
		pila.or();
		pc++;
	}
	//metodo para ejecutar la funcion and
	public void and()throws IOException{
		traza("And");
		pila.and();
		pc++;
	}
	//metodo para ejecutar la funcion negacion
	public void negacion()throws IOException{
		traza("Negacion");
		pila.negacion();
		pc++;
	}
	//metodo para ejecutar la funcion menos
	public void menos()throws IOException{
		traza("Cambio de signo");
		pila.menos();
		pc++;
	}
	//metodo para ejecutar la funcion casting int
	public void castInt()throws IOException{
		traza("Casting a entero");
		pila.castInt();
		pc++;
	}
	//metodo para ejecutar la funcion casting real
	public void castReal()throws IOException{
		traza("Casting a real");
		pila.castReal();
		pc++;
	}
	//metodo para hacer la traza, solo necesita que le indiquen el tipo de operacion realizada
	private void traza(String codigo) throws IOException{
		if(tipo == 1){
			System.out.println(codigo);//muestra el tipo de operacion
			System.out.println("Codigo a ejecutar : "+linea);//la instruccion que tiene que ejecutar
			System.out.println("Pila del proceso : "+pila);//como se encuentra la pila 
			System.out.println("Memoria de datos : " + variables.toString());//como se encuentra la memoria de datos
			System.out.println("Pulse ENTER para continuar");//escribe esto para que el usuario lo pulse para continuar
			BufferedReader br = new BufferedReader(new InputStreamReader(System.in));
			br.readLine();
		}
		
	}
	
}
